﻿#region License
/*
	Copyright (c) 2008, Sijmen Mulder
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:

	- Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    - Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
	- Neither the name of the author nor the names of its contributors may be
      used to endorse or promote products derived from this software without
      specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Globalization;

namespace SharpFoundation.PlistLib
{
	public class PlistItem
	{
		protected PlistUnarchiver unarchiver;
		protected Dictionary<string, object> objectCache;
		protected XmlNode xmlNode;
		
		Dictionary<string, PlistItem> keyedItems;
		List<PlistItem> unkeyedItems;

		object decodedObject;
		bool decodingFailed;

		bool hasVectorValue;
		bool hasRectangleValue;

		SizeF vectorValue;
		RectangleF rectangleValue;

		public PlistItem(PlistUnarchiver unarchiver, Dictionary<string, object> objectCache, 
			XmlNode xmlNode)
		{
			this.unarchiver = unarchiver;
			this.objectCache = objectCache;
			this.xmlNode = xmlNode;
			
			keyedItems = new Dictionary<string, PlistItem>();
			unkeyedItems = new List<PlistItem>();

			foreach (XmlNode childNode in xmlNode.ChildNodes)
			{
				if (childNode.NodeType != XmlNodeType.Element)
					continue;

				PlistItem item = new PlistItem(unarchiver, objectCache, childNode);
				XmlAttribute keyAttribute = childNode.Attributes["key"];
	
				if (keyAttribute != null)
					keyedItems[keyAttribute.Value] = item;
				else
					unkeyedItems.Add(item);
			}

			decodedObject = null;
			decodingFailed = false;

			hasVectorValue = false;
			hasRectangleValue = false;
		}

		public bool GetBool(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].BoolValue;
			else
				return false;
		}

		public int GetInt(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].IntValue;
			else
				return 0;
		}

		public long GetLong(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].LongValue;
			else
				return 0;
		}

		public float GetFloat(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].FloatValue;
			else
				return 0.0f;
		}

		public double GetDouble(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].DoubleValue;
			else
				return 0.0;
		}

		public string GetString(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].StringValue;
			else
				return null;
		}

		public SizeF GetSize(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].SizeValue;
			else
				return SizeF.Empty;
		}

		public PointF GetPoint(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].PointValue;
			else
				return PointF.Empty;
		}

		public RectangleF GetRectangle(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].RectangleValue;
			else
				return RectangleF.Empty;
		}

		public object GetObject(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].ObjectValue;
			else
				return null;
		}

		public object[] GetArray(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].ArrayValue;
			else
				return null;
		}

		public Dictionary<object, object> GetDictionary(string key)
		{
			if (keyedItems.ContainsKey(key))
				return keyedItems[key].DictionaryValue;
			else
				return null;
		}

		public double DoubleValue
		{
			get
			{ 
				string str = xmlNode.InnerText;
				str = str.Replace(".", ",");

				return double.Parse(str, NumberStyles.Any);
			}
		}
		
		public bool BoolValue { get { return xmlNode.InnerText == "YES"; } }
		public int IntValue { get { return Convert.ToInt32(xmlNode.InnerText); } }
		public long LongValue { get { return Convert.ToInt64(xmlNode.InnerText); } }
		public float FloatValue { get { return (float)DoubleValue; } }
		public string StringValue { get { return xmlNode.InnerText; } }

		public SizeF SizeValue
		{
			get
			{
				if (hasVectorValue)
					return vectorValue;

				string str = xmlNode.InnerText;
				str = str.Substring(1, str.Length - 2);

				string[] parts = str.Split(',');
				vectorValue.Width = Convert.ToSingle(parts[0]);
				vectorValue.Height = Convert.ToSingle(parts[1]);

				hasVectorValue = true;

				return vectorValue;
			}
		}

		public PointF PointValue
		{
			get { return SizeValue.ToPointF(); }
		}

		public RectangleF RectangleValue
		{
			get
			{
				if (hasRectangleValue)
					return rectangleValue;

				string str = xmlNode.InnerText;
				str = str.Replace("{", "");
				str = str.Replace("}", "");

				string[] parts = str.Split(',');
				rectangleValue.X = Convert.ToSingle(parts[0]);
				rectangleValue.Y = Convert.ToSingle(parts[1]);
				rectangleValue.Width = Convert.ToSingle(parts[2]);
				rectangleValue.Height = Convert.ToSingle(parts[3]);

				hasRectangleValue = true;

				return rectangleValue;
			}
		}

		public object ObjectValue
		{
			get
			{
				if (decodedObject != null)
					return decodedObject;
				else if (decodingFailed)
					return null;

				bool needsObjectId = false;
				string objectId = null;

				if (xmlNode.Name == "reference")
				{
					XmlAttribute refAttribute = xmlNode.Attributes["ref"];
					if (refAttribute != null)
						objectId = refAttribute.Value;

					needsObjectId = true;
				}
				else if (xmlNode.Name == "object")
				{
					XmlAttribute idAttribute = xmlNode.Attributes["id"];
					if (idAttribute != null)
						objectId = idAttribute.Value;

					needsObjectId = false;
				}
				
				if (objectId == null)
				{
					if (needsObjectId)
					{
						decodingFailed = true;
						return null;
					}
				}
				else
				{
					if (objectCache.ContainsKey(objectId))
						return objectCache[objectId];

					if (xmlNode.Name == "reference")
					{
						unarchiver.PrintIndentation();
						Console.WriteLine("? ->{0}", objectId);

						decodingFailed = true;
						return null;
					}
				}

				string objectClassName = xmlNode.Attributes["class"].Value;
				decodedObject = unarchiver.DecodeObject(objectClassName, this);
				
				if (decodedObject != null)
				{
					if (objectId != null)
						objectCache[objectId] = decodedObject;
				}
				else
				{
					decodingFailed = true;
				}

				return decodedObject;
			}
		}

		public object[] ArrayValue
		{
			get { return ObjectValue as object[]; }
		}

		public Dictionary<object, object> DictionaryValue
		{
			get { return ObjectValue as Dictionary<object, object>; }
		}

		public IDictionary<string, PlistItem> KeyedItems { get { return keyedItems; } }
		public IList<PlistItem> UnkeyedItems { get { return unkeyedItems.AsReadOnly(); } }
	}
}
